package com.ouyu.mvc.servlet;


import com.alibaba.fastjson.JSONObject;
import com.ouyu.mvc.propertity.ReadProperty;
import com.ouyu.mvc.proxy.MethodProxy;
import com.ouyu.mvc.selfAnnotation.*;
import com.ouyu.mvc.selfAnnotation.JController;
import com.ouyu.mvc.selfAnnotation.JRepository;
import com.ouyu.mvc.selfAnnotation.JService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.*;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebInitParam;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.net.URL;
import java.util.*;

/**
 * Created with IntelliJ IDEA.
 * Description:
 * User: ouyu
 * Date: 2018-10-24
 * Time: 10:42
 */
@Slf4j
@WebServlet(name="dispatchServlet",urlPatterns = "/*",loadOnStartup = 1,
        initParams = {@WebInitParam(name = "base-package",value = "")})
public class DispatchServlet extends HttpServlet{

    @Autowired
    private ReadProperty readProperty;

    //扫描的基包
    private String basePackage="";
    //基包下所有包含报名路劲权限的类名
    private List<String> packageNames = new ArrayList<>();
    //注解实例化 类注解的名称：实例化对象
    private Map<String,Object> instanceMap = new HashMap<>();
    //带包名的路径限定名称：类注解的名称
    private Map<String, String> nameMap = new HashMap<>();
    //url地址和方法的映射关系 springMvc就是方法调用链
    private Map<String, Method> urlMap = new HashMap<>();
    //Method和包限定名的映射关系 主要是为了通过method找到该方法的对象利用反射执行
    private Map<Method, String> methodPackageMap = new HashMap<>();
    @Override
    public void init(ServletConfig config) throws ServletException{
        String basicPackage = config.getInitParameter("base-package");
        if(StringUtils.isBlank(basicPackage)){
            InputStream in = null;
            try {
                String filePath = System.getProperty("user.dir") + "/src/main/resources/bootstrap.properties";
                //in = this.getClass().getClassLoader().getResourceAsStream(filePath);
                File file =new File(filePath);
                in = new FileInputStream(file);
                Properties properties =new Properties();
                properties.load(in);
               basicPackage = (String)properties.get("jlittle.basePackage");
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        if(StringUtils.isBlank(basicPackage)){
            log.error("未配置扫描的基包路径base-package:{}");
            throw new RuntimeException("未配置扫描的基包路径base-package:{}");
        }
        log.info("配置扫描的基包路径base-package:{}",basicPackage);
        basePackage = basicPackage;
        try {
            //1.扫描基包得到全部的带包名路径全限定名
            scanBasePackage(basicPackage);
            //2.把带有@Contorller/@Server/@Repository注解的类实例化放入到map中 key为注解的名称
            instance(packageNames);
            //3.实现像springIoc DI一样的依赖注入
            springIoc();
            //4.完成url地址与方法的映射关系
            handleUrlMethodMap();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        }

    }
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response){
        doPost(request,response);
    }
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response){
        String url = request.getRequestURI();
        String contextpath = request.getContextPath();
        String path = url.replaceAll(contextpath,"");
        Method method = urlMap.get(path);
        if(method != null){
            String packageName = methodPackageMap.get(method);
           String controllerName = nameMap.get(packageName);

            Object object=instanceMap.get(controllerName);
            try {
                method.setAccessible(true);
                  Class[] c = method.getParameterTypes();
                  if(c.length>0){
                      String rt=null;
                      String rn=null;
                      Object[] arv= new Object[2];
                      int i=0;
                      for (Class cl:c){
                          System.out.println(cl.getClass()+"方法参数");
                          System.out.println(cl.getTypeName()+"方法参数");
                          if("javax.servlet.http.HttpServletRequest".equals(cl.getTypeName())){
                              rt=cl.getTypeName();
                              arv[i]=request;
                              i++;
                          }else if("javax.servlet.http.HttpServletResponse".equals(cl.getTypeName())){
                              rn=cl.getTypeName();
                              arv[i]=response;
                              i++;
                          }
                      }
                      invokeAndReturn(method,request,response,object,arv);

                  }else{
                      invokeAndReturn(method,request,response,object,null);
                  }

            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }

        }
    }
    private void invokeAndReturn(Method method,HttpServletRequest request,HttpServletResponse response,Object object,Object[] arv) throws InvocationTargetException, IllegalAccessException, IOException {
        Class returnType = method.getReturnType();
        if("void".equals(returnType.getTypeName())){
            method.invoke(object,arv);
        }else{
            Object o=null;
            if(arv==null){
               o= method.invoke(object,arv);
            }else{
               o= method.invoke(object,arv);
            }

            if(o instanceof String){
                response.getWriter().print(o.toString());
            }else if(o instanceof JSONObject){
                response.setContentType("application/json;charset=utf-8");//指定返回的格式为JSON格式
                response.setCharacterEncoding("UTF-8");
                response.getWriter().print(o);
            }else if(method.isAnnotationPresent(JResponseBody.class)){
                response.setContentType("application/json;charset=utf-8");//指定返回的格式为JSON格式
                response.setCharacterEncoding("UTF-8");
                response.getWriter().print(JSONObject.toJSONString(o));
            }
        }
    }

    private void handleUrlMethodMap() throws ClassNotFoundException {
        for(Map.Entry<String,String > entry:nameMap.entrySet()){
            String packageName =entry.getKey();
            Class className = Class.forName(packageName);
            String oneRequestMappingName="";
            if(className.isAnnotationPresent(JRequestMapping.class)){
                JRequestMapping requestMapping = (JRequestMapping) className.getAnnotation(JRequestMapping.class);
                oneRequestMappingName = requestMapping.value();

            }
            Method[] methods = className.getMethods();
            for(Method method:methods){
                if(method.isAnnotationPresent(JRequestMapping.class)){
                    JRequestMapping requestMapping = method.getAnnotation(JRequestMapping.class);
                    urlMap.put(oneRequestMappingName+requestMapping.value(),method);
                    methodPackageMap.put(method,packageName);
                }
            }
        }
    }

    /**
     * 3.实现像springIoc DI一样的依赖注入
     * @throws ClassNotFoundException
     * @throws IllegalAccessException
     */
    private void springIoc() throws ClassNotFoundException, IllegalAccessException {
       for(Map.Entry<String,Object> entry: instanceMap.entrySet()){
          Field[] fields = entry.getValue().getClass().getDeclaredFields();
          for(Field field:fields){
              if(field.isAnnotationPresent(JAutoWrite.class)){
                  JAutoWrite autoWrite = (JAutoWrite)field.getAnnotation(JAutoWrite.class);
                  String autoWriteValue = autoWrite.value();
                  //通过反射给字段赋值需要强制执行 设为true
                  field.setAccessible(true);
                  //通过反射给字段赋值需要把当前字段所在类的实例和值同时放入 这边给字段付的值其实是通过类注解得到的实例对象
                  field.set(entry.getValue(),instanceMap.get(autoWriteValue));
              }
          }
       }
    }

    /**
     * 把带有@Contorller/@Server/@Repository注解的类实例化放入到map中 key为注解的名称
     * @param packageNames
     * @throws ClassNotFoundException
     * @throws IllegalAccessException
     * @throws InstantiationException
     */
    private void instance(List<String> packageNames) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
        if(packageNames.size() < 1){
            return;
        }
        for (String packageName:packageNames){
            Class clasName = Class.forName(packageName);
            if(clasName.isAnnotationPresent(JController.class)){
                JController controller =(JController) clasName.getAnnotation(JController.class);
                instanceMap.put(controller.value(),clasName.newInstance());
                nameMap.put(packageName,controller.value());
                log.info("controller:{},value:{} 成功",packageName,controller.value());
            }else if(clasName.isAnnotationPresent(JService.class)){
                JService service = (JService)clasName.getAnnotation(JService.class);
                String serviceValue =service.value();
                instanceMap.put(serviceValue,clasName.newInstance());
                //nameMap.put(packageName,serviceValue);
                log.info("service:{},value:{} 成功",packageName,service.value());
            }else if(clasName.isAnnotationPresent(JRepository.class)){
                JRepository repository = (JRepository)clasName.getAnnotation(JRepository.class);
                String repositoryValue =repository.value();
                MethodProxy mp=new MethodProxy();
                instanceMap.put(repositoryValue,mp.getInstance(clasName.newInstance()));
                //nameMap.put(packageName,repositoryValue);
                log.info("service:{},value:{} 成功",packageName,repository.value());
            }
        }
    }

    /**
     * 1.扫描基包得到全部的带包名路径全限定名
     * @param basicPackage
     */
    private void scanBasePackage(String basicPackage) {
        URL url =this.getClass().getClassLoader().getResource(basicPackage.replaceAll("\\.","/"));
        File file = new File(url.getPath());
        File[] childFiles =file.listFiles();
        for (File childFile:childFiles){
            if(childFile.isDirectory()){//目录继续递归扫描
                scanBasePackage(basicPackage+"."+childFile.getName());
            } else if (childFile.isFile()) {
                //把类的限定名存入 像com.ouyu.mvc.servlet.DispatchServlet
                packageNames.add(basicPackage+"."+childFile.getName().split("\\.")[0]);
            }
        }
    }

}
